using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using MyClever.Lib;
using System.Xml;

namespace MyCleverMissionEditor
{    
    public partial class Form1 : Form
    {
        private Mission mission;
        private DateTime lastFileSystemWatcherEventTime;
        private LogTabPage loggingTabPage;
        private string missionFilePath;

        public string MissionFilePath
        {
            get { return missionFilePath; }
            set 
            {
                this.missionFilePath = value;

                if (false == String.IsNullOrEmpty(value))
                {
                    this.tabPageMainXmlCode.Text = value;

                    // update the fileWatcher and enable fileWatcher again
                    FileInfo file = new FileInfo(this.MissionFilePath);
                    this.fileSystemWatcher1.Path = file.DirectoryName;
                    this.fileSystemWatcher1.Filter = file.Name;
                }
                else
                {
                    this.tabPageMainXmlCode.Text = "untitled";                 
                }

                this.Text = "MyCleverMissionEditor - " + this.tabPageMainXmlCode.Text;
            }
        }
       
        public Form1()
        {
            InitializeComponent();

            this.tabPageMainDebugger.PluginAdded += new DragEventHandler(tabPageMainDebugger_PluginAdded);
            this.lastFileSystemWatcherEventTime = DateTime.Now;
            
            this.loggingTabPage = new LogTabPage();
            this.tabControlLogging.Controls.Add(this.loggingTabPage);
            this.loggingTabPage.LoggingFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logging.log");
        }

        void tabPageMainDebugger_PluginAdded(object sender, DragEventArgs e)
        {
            MissionPluginProxy newPlugin = new MissionPluginProxy((AvailablePlugin)e.Data.GetData("MyClever.Lib.AvailablePlugin"));
            this.mission.PluginCollection.Add(newPlugin, this.tabPageMainDebugger.RelativePluginPosition);
            
            this.UpdateForm();
        }

        private void ResetForm()
        {
            this.mission = new Mission();

            this.richTxtBxMissionXmlCode.Text = "";
            this.dataGridView1.DataSource = null;           
            this.MissionFilePath = "";

            this.treeVwAppliedPlugins.Nodes.Clear();

            this.UpdateForm();
        }

        public void UpdateForm()
        {
            this.UpdateMissionTreeView();
            this.tabPageMainDebugger.Refresh(this.mission.PluginCollection);
        }       

        public void UpdateMissionTreeView()
        {
            // delete old mission stuff
            this.treeVwAppliedPlugins.Nodes.Clear();

            TreeNode rootNode = this.treeVwAppliedPlugins.Nodes.Add(mission.Name);
            rootNode.ImageIndex = 0;

            this.CreateMissionTreeViewCollection(this.mission.PluginCollection, ref rootNode);
            rootNode.Expand();
        }

        private void CreateMissionTreeViewCollection(MissionPluginLoopNode pluginProxyNode, ref TreeNode rootNode)
        {
            // get all plugins
            foreach (MissionPluginNode pluginNodeOn in pluginProxyNode.PluginNodes)
            {
                if (typeof(MissionPluginLoopNode) == pluginNodeOn.GetType())
                {
                    MissionPluginLoopNode nextCollection = (MissionPluginLoopNode)pluginNodeOn;

                    TreeNode nextLoopNode = rootNode.Nodes.Add("LoopThroughEveryPackageRow");
                    nextLoopNode.Tag = nextCollection;
                    nextLoopNode.ImageIndex = 4;
                    this.CreateMissionTreeViewCollection(nextCollection, ref nextLoopNode);
                    nextLoopNode.Expand();
                }
                else //MissionPluginProxy
                {
                    MissionPluginProxy pluginOn = (MissionPluginProxy)pluginNodeOn;

                    TreeNode pluginNode = rootNode.Nodes.Add(pluginOn.ToString());
                    pluginNode.Tag = pluginOn.ID;
                    pluginNode.ImageIndex = 7;
                    pluginNode.Expand();

                    foreach (MissionPluginPackage packageOn in mission.GetPluginByID(pluginOn.ID).GetPackages(Direction.All))
                    {
                        TreeNode packageNode = pluginNode.Nodes.Add(packageOn.ToString());
                        packageNode.Tag = packageOn;
                        // select the correct icon for this package-type
                        switch (Convert.ToInt32(packageOn.Direction))
                        {
                            case 1:
                                packageNode.ImageIndex = 1;
                                break;
                            case 2:
                                packageNode.ImageIndex = 2;
                                break;
                            case 4:
                                packageNode.ImageIndex = 3;
                                break;
                            default:
                                break;
                        }

                        packageNode.ToolTipText = packageOn.Description;

                        if (true == packageOn.IsSoftPackage)
                        {
                            packageNode.ForeColor = Color.SlateGray;
                        }

                        foreach (MissionPluginPackageColumn columnOn in packageOn.Columns)
                        {
                            TreeNode columnNode = packageNode.Nodes.Add(columnOn.ToString());
                            columnNode.Tag = columnOn;
                            columnNode.ImageIndex = 5;
                            if (true == columnOn.HasColumnReference)
                            {
                                columnNode.ImageIndex = 6;
                                packageNode.Expand();
                            }
                        }
                    }
                }
            }
        }

        public void GenerateMissionCode()
        {
            MemoryStream missionXMLCode = new MemoryStream();
            MissionXMLWriter.WriteRootElement(ref missionXMLCode, ref this.mission);

            // write the generated mission-xml-code in the textbox
            StreamReader streamReader = new StreamReader(missionXMLCode);
            missionXMLCode.Seek(0, SeekOrigin.Begin);
            this.richTxtBxMissionXmlCode.Text = streamReader.ReadToEnd();
            streamReader = null;
            missionXMLCode = null;
        }

        private void Form1_Load(object sender, EventArgs e)
        {            
            this.ResetForm();
            
            this.treeVwAppliedPlugins.Nodes.Clear();
            this.treeVwAppliedPlugins.ImageList = this.missionIcons;

            this.treeVwAvailablePlugins.Nodes.Clear();
            this.treeVwAvailablePlugins.ImageList = this.missionIcons;

            // Get all available plugins
            PluginManager.Instance.LoadPluginsFromDirectory();
            
            //Go through all the files in the plugin directory
            foreach (AvailablePlugin pluginOn in PluginManager.Instance.Plugins)
            {
                TreeNode availablePlugin = new TreeNode(pluginOn.Name);
                availablePlugin.Tag = pluginOn;

                if (ProgrammingLanguage.CSharp == pluginOn.Language)
                {
                    availablePlugin.ImageIndex = 7;
                }
                else
                {
                    availablePlugin.ImageIndex = 8;
                }
                this.treeVwAvailablePlugins.Nodes.Add(availablePlugin);
            }
        }
        
        private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
        {
            // while the fileSystemWatcher calls multiply events for one action, we handle the first event only
            TimeSpan timeSpanSinceLastEvent = DateTime.Now.Subtract(this.lastFileSystemWatcherEventTime);
            if (timeSpanSinceLastEvent.Seconds > 1)
	        {
                this.lastFileSystemWatcherEventTime = DateTime.Now;

                MessageBox.Show(this.MissionFilePath + " was changed!");

                StreamReader streamReader = new StreamReader(this.MissionFilePath);
                this.richTxtBxMissionXmlCode.Text = streamReader.ReadToEnd();
                streamReader.Close();
	        }
        }

        private void treeVwAvailablePlugins_AfterSelect(object sender, TreeViewEventArgs e)
        {
            this.treeVwAvailablePackages.Nodes.Clear();

            AvailablePlugin selectedPlugin = (AvailablePlugin)e.Node.Tag;

            // don't change the treeNode image, if the item was selected
            this.treeVwAvailablePlugins.SelectedImageIndex = e.Node.ImageIndex;

            //foreach (MissionPluginPackage packageOn in selectedPlugin.GetPackages())
            //{
            //    TreeNode availablePackage = new TreeNode(packageOn.TableName);
            //    availablePackage.Tag = packageOn;
            //    availablePackage.ToolTipText = packageOn.Description;
            //    this.treeVwAvailablePackages.Nodes.Add(availablePackage);
            //}
        }

        private void treeVwAppliedPlugins_AfterSelect(object sender, TreeViewEventArgs e)
        {
            // don't switch the tree-image, if we select the treenode?
            // todo: there must be a better way.
            TreeView tview = (TreeView)sender;
            this.treeVwAppliedPlugins.SelectedImageIndex = tview.SelectedNode.ImageIndex;
            
            // has the user select a package?
            if (null != e.Node.Tag && typeof(MissionPluginPackage) == e.Node.Tag.GetType())
            {
                DataGridViewCellStyle cellStyle = new DataGridViewCellStyle();
                cellStyle.BackColor = Color.SlateGray;
                cellStyle.ForeColor = Color.Snow;

                MissionPluginPackage packageOn = (MissionPluginPackage)e.Node.Tag;
                this.dataGridView1.DataSource = packageOn;
                this.dataGridView1.Visible = true;

                foreach (MissionPluginPackageColumn columnOn in packageOn.Columns)
                {
                    // mark all linked datastream-columns with a special color
                    if (null != columnOn.ExtendedProperties[MissionPluginPackageColumn.ExtendedPropertiesKey.UseDataStream])
                    {
                        this.dataGridView1.Columns[columnOn.ColumnName].HeaderCell.Style = cellStyle;
                    }

                    // Bugfix: while the datagrid doesn't delete the old toolTip in some cases, we reset them
                    this.dataGridView1.Columns[columnOn.ColumnName].ToolTipText = "";
                    // add columnDescription as toolTip
                    this.dataGridView1.Columns[columnOn.ColumnName].ToolTipText = columnOn.Description;
                }
                this.dataGridView1.EnableHeadersVisualStyles = false;

                this.tabControlMain.SelectedTab = this.tabPageMainPackages;
            }
        }

        private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            MissionPluginPackage packageOn = (MissionPluginPackage)dataGridView1.DataSource;
            MissionPluginPackageColumn columnOn = (MissionPluginPackageColumn)packageOn.Columns[e.ColumnIndex];

            if (columnOn.HasValidation)
            {
                if (false == ValidationService.Instance.IsValid(columnOn.Validation, e.FormattedValue.ToString()))
                {
                    dataGridView1.Rows[e.RowIndex].ErrorText = ValidationService.Instance.ErrorMessages;
                    e.Cancel = true;
                }
            }
        }

        private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            // Clear the row error in case the user presses ESC.   
            this.dataGridView1.Rows[e.RowIndex].ErrorText = String.Empty;
        }

        private void toolStripBtnStartMission_Click(object sender, EventArgs e)
        {
            this.backgroundWorker1.RunWorkerAsync();
        }

        private void toolStripBtnLoadMissionFile_Click(object sender, EventArgs e)
        {
            this.openFileDialog1.Filter = "Mission files (*.xml)|*.xml|All files (*.*)|*.*";
            this.openFileDialog1.InitialDirectory = ".\\Missions\\";

            if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                this.ResetForm();
                this.MissionFilePath = this.openFileDialog1.FileName;

                // reads the content from the expected file in the richTextbox
                FileStream fileStream = new FileStream(this.MissionFilePath, FileMode.Open);
                StreamReader sr = new StreamReader(fileStream);
                this.richTxtBxMissionXmlCode.Text = sr.ReadToEnd();
                sr.Close();
                fileStream.Close();

                // update the mission instance
                this.mission = new Mission(this.openFileDialog1.FileName);
                this.mission.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(MissionProgressChanged);

                this.UpdateForm();
            }
        }

        private void toolStripBtnSaveMission_Click(object sender, EventArgs e)
        {           
            this.saveFileDialog1.Filter = "Mission files (*.xml)|*.xml|All files (*.*)|*.*";

            if (false == String.IsNullOrEmpty(this.MissionFilePath))
            {
                FileInfo saveFileInfo = new FileInfo(this.MissionFilePath);
                this.saveFileDialog1.InitialDirectory = saveFileInfo.DirectoryName;
                this.saveFileDialog1.FileName = saveFileInfo.Name;   
            }

            if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                // disable the fileWatcher as long we write new content in the mission-file
                this.fileSystemWatcher1.EnableRaisingEvents = false;
                
                // saves the content from the richTextbox in the expected file
                FileStream fileStream = new FileStream(this.saveFileDialog1.FileName, FileMode.Create);
                StreamWriter sw = new StreamWriter(fileStream);
                sw.Write(this.richTxtBxMissionXmlCode.Text);
                sw.Close();
                fileStream.Close();

                this.MissionFilePath = this.saveFileDialog1.FileName;
                // enable the fileWatcher
                this.fileSystemWatcher1.EnableRaisingEvents = true;            
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            this.mission.Start();
        }

        private void MissionProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.backgroundWorker1.ReportProgress(e.ProgressPercentage);
        }     
        
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.UpdateForm();
            
            // TODO: use Nlog here!!
            foreach (MissionPluginProxy pluginOn in this.mission.GetDefectivePlugins())
            {
                this.loggingTabPage.AppendText("--------ATTENTION---------");
                this.loggingTabPage.AppendText(String.Format("plugin '{0}' (id:{1}) (version: {2}) execute following errors:", pluginOn.ClassName, pluginOn.ID, pluginOn.GetProperty(PluginProperty.Version)));
                
                foreach (string errorMessage in pluginOn.GetErrorMessages())
                {
                    this.loggingTabPage.AppendText(errorMessage);
                }
            }
        }

        private void toolStripBtnNewMission_Click(object sender, EventArgs e)
        {
            this.ResetForm();
        }

        private void tabControlMain_Selected(object sender, TabControlEventArgs e)
        {
            // was mission-code tab selected?
            if (0 == e.TabPageIndex)
	        {
                this.GenerateMissionCode();
	        }
        }

        //TODO: we should send the selected package to the WriteCSVFile-plugin!
        private string ExportPackageAsCSVData(ref MissionPluginPackage packageToExport)
        {
            DataTableReader fileContentPackage = packageToExport.CreateDataReader();
            
            StringBuilder textData = new StringBuilder(1000);
            // write the csv-header
            foreach (MissionPluginPackageColumn columnOn in packageToExport.Columns)
            {
                textData.Append("\"" + columnOn.ToString().Replace("\"", "\"\"") + "\",");
            }
            // delete the last commata
            textData.Remove(textData.Length - 1, 1);
            // append a new breakline
            textData.Append("\n");

            // write the csv-data            
            foreach (DataRow rowOn in packageToExport.Rows)
            {
                foreach (MissionPluginPackageColumn columnOn in packageToExport.Columns)
                {
                    textData.Append("\"" + rowOn[columnOn].ToString().Replace("\"", "\"\"") + "\",");
                }
                // delete the last commata
                textData.Remove(textData.Length - 1, 1);
                // append a new breakline
                textData.Append("\n");
            }
            return textData.ToString();
        }

        private string ExportPackageAsValidationData(ref MissionPluginPackage packageToExport)
        {
            MemoryStream missionXMLCode = new MemoryStream();
            XmlTextWriter xmlWriter = new XmlTextWriter(missionXMLCode, null);
            xmlWriter.Formatting = Formatting.Indented;
            xmlWriter.WriteStartDocument();

            xmlWriter.WriteStartElement("Data");
            foreach (DataRow rowOn in packageToExport.Rows)
            {
                xmlWriter.WriteStartElement("Row");
                foreach (MissionPluginPackageColumn columnOn in packageToExport.Columns)
                {
                    xmlWriter.WriteElementString(columnOn.ColumnName, rowOn[columnOn].ToString());
                }
                xmlWriter.WriteEndElement(); // "Row"
            }
            xmlWriter.WriteEndElement(); // "Data"

            xmlWriter.WriteEndDocument();
            xmlWriter.Flush();

            StreamReader streamReader = new StreamReader(missionXMLCode);
            missionXMLCode.Seek(0, SeekOrigin.Begin);
            string xmlCode = streamReader.ReadToEnd();
            streamReader = null;
            missionXMLCode = null;
            
            return xmlCode;
        }

        private void ExportCSVFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (null != this.dataGridView1.DataSource)
            {              
                MissionPluginPackage packageOn = (MissionPluginPackage)this.dataGridView1.DataSource;
                
                this.saveFileDialog1.Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*";
                this.saveFileDialog1.FileName = packageOn.TableName;

                if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
                {
                    // saves the content from the richTextbox in the expected file
                    FileStream fileStream = new FileStream(this.saveFileDialog1.FileName, FileMode.Create);
                    StreamWriter sw = new StreamWriter(fileStream);
                    sw.Write(this.ExportPackageAsCSVData(ref packageOn));
                    sw.Close();
                    fileStream.Close();
                }
            }
            else
            {
                MessageBox.Show("You must select a valid package first!");
            }
        }

        // TODO: this is the same code above!! i don't like this!
        private void ExportValidationXMLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (null != this.dataGridView1.DataSource)
            {
                MissionPluginPackage packageOn = (MissionPluginPackage)this.dataGridView1.DataSource;

                this.saveFileDialog1.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*";
                this.saveFileDialog1.FileName = packageOn.TableName;

                if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
                {
                    // saves the content from the richTextbox in the expected file
                    FileStream fileStream = new FileStream(this.saveFileDialog1.FileName, FileMode.Create);
                    StreamWriter sw = new StreamWriter(fileStream);
                    sw.Write(this.ExportPackageAsValidationData(ref packageOn));
                    sw.Close();
                    fileStream.Close();
                }
            }
            else
            {
                MessageBox.Show("You must select a valid package first!");
            }
        }

        private void treeVwAvailablePlugins_ItemDrag(object sender, ItemDragEventArgs e)
        {
            this.DoDragDrop(((TreeNode)e.Item).Tag, DragDropEffects.Move);
        }

        private void treeVwAppliedPlugins_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("MyClever.Lib.AvailablePlugin", false))
            {
                e.Effect = DragDropEffects.Move;
            }
        }

        private void treeVwAppliedPlugins_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("MyClever.Lib.AvailablePlugin", false))
            {
                MissionPluginProxy newPlugin = new MissionPluginProxy((AvailablePlugin)e.Data.GetData("MyClever.Lib.AvailablePlugin"));
                
                //this.mission.PluginCollection.Append(newPlugin);

                PackageManager packageManager = new PackageManager(ref this.mission, newPlugin.ID);
                packageManager.MissionEditorForm = this;
                packageManager.Show();
            }
        }

        private void treeVwAppliedPlugins_DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("MyClever.Lib.AvailablePlugin", false))
            {
                Point pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
                TreeNode pluginRootNode = this.GetNextParentByImageIndex(((TreeView)sender).GetNodeAt(pt), 1);

                // we must draw a placeholder only, when there is at least one plugin registered
                if (null != pluginRootNode)
                {
                    //Todo: Refresh not every time
                    this.Refresh();
                    this.DrawPlaceholder(pluginRootNode, this.IsPreviousTreeNode(this.GetVeryLastChildNode(pluginRootNode), pt.Y));
                }
            }
        }

        private void DrawPlaceholder(TreeNode nodeOver, bool drawAboveNode)
        {
            Graphics g = this.tabPageMainDebugger.CreateGraphics();

            int nodeOverImageWidth = this.treeVwAppliedPlugins.ImageList.Images[nodeOver.ImageIndex].Size.Width + 8;
            int placeholderLeftPos = nodeOver.Bounds.Left - nodeOverImageWidth;
            int placeholderRightPos = this.treeVwAppliedPlugins.Width - 4;

            // whenever the pluginRootNode is expanded, we must draw the placeholder
            // among the last (visible) childnode
            if (false == drawAboveNode)
            {
                nodeOver = this.GetVeryLastChildNode(nodeOver);
            }
            
            // should we draw the placeholder above or among the given treenode?
            int posY = nodeOver.Bounds.Top;
            if (false == drawAboveNode)
            {
                posY = nodeOver.Bounds.Bottom;
            }
            
            Point[] leftTriangle = new Point[5]{
												   new Point(placeholderLeftPos, posY - 4),
												   new Point(placeholderLeftPos, posY + 4),
												   new Point(placeholderLeftPos + 4, posY),
												   new Point(placeholderLeftPos + 4, posY - 1),
												   new Point(placeholderLeftPos, posY - 5)};

            Point[] rightTriangle = new Point[5]{
													new Point(placeholderRightPos, posY - 4),
													new Point(placeholderRightPos, posY + 4),
													new Point(placeholderRightPos - 4, posY),
													new Point(placeholderRightPos - 4, posY - 1),
													new Point(placeholderRightPos, posY - 5)};


            g.FillPolygon(System.Drawing.Brushes.Black, leftTriangle);
            g.FillPolygon(System.Drawing.Brushes.Black, rightTriangle);
            g.DrawLine(new System.Drawing.Pen(Color.Black, 2), new Point(placeholderLeftPos, posY), new Point(placeholderRightPos, posY));
        }

        private TreeNode GetNextParentByImageIndex(TreeNode node, int imageIndex)
        {
            while (null != node && imageIndex != node.ImageIndex)
            {
                // save the parent
                node = node.Parent;
            }
            return node;
        }

        private TreeNode GetVeryLastChildNode(TreeNode node)
        {
            while (null != node && null != node.LastNode && node.LastNode.IsVisible)
            {
                // save the parent
                node = node.LastNode;
            }
            return node;
        }

        private bool IsPreviousTreeNode(TreeNode node, int mousePosY)
        {
            int nodeCollectionHeight = node.Bounds.Bottom - node.Bounds.Top;
            int offsetY = mousePosY - node.Bounds.Top;

            return offsetY < (nodeCollectionHeight / 2);
        }

        private void treeVwAppliedPlugins_ItemDrag(object sender, ItemDragEventArgs e)
        {
            TreeNode draggedNode = (TreeNode)e.Item;
            
            Dictionary<string, object> referenceData = new Dictionary<string, object>();
            referenceData.Add("FromPluginID", this.GetNextParentByImageIndex(draggedNode, 1).Tag.ToString());
            
            if (typeof(MissionPluginPackage) == draggedNode.Tag.GetType())
            {
                referenceData.Add("FromPackage", ((MissionPluginPackage)draggedNode.Tag).TableName);
                this.DoDragDrop(referenceData, DragDropEffects.Move);
            }
            if (typeof(MissionPluginPackageColumn) == draggedNode.Tag.GetType())
            {
                referenceData.Add("FromPackage", ((MissionPluginPackage)draggedNode.Parent.Tag).TableName);                
                referenceData.Add("FromColumn", ((MissionPluginPackageColumn)draggedNode.Tag).ColumnName);
                this.DoDragDrop(referenceData, DragDropEffects.Move);
            }
        }
    }
}